"""Functionality for the Ruin World"""

from pyre.core import types, actor, dice
from pyre.gen import cave, terrain
from pyre.data import info
from ruin import character, player, monster, item, resource

class RuinWorld(actor.World):
    """Base class for Ruin Worlds"""

    def __init__(self, cols, rows):
        actor.World.__init__(self, cols, rows)
        self.act_order = [player.Player, monster.Monster]
        self.look_order = [character.Character, item.Item]

        self._uplinks = {}
        self._downlinks = {}

    def _connect_worlds(self, other, pos, other_pos, stair, other_stair):
        assert self is not other

        self._downlinks[pos] = (other, other_pos)
        other._uplinks[other_pos] = (self, pos)

        if stair:
            self.set_tile(pos, stair, True)
        if other_stair:
            other.set_tile(other_pos, other_stair, True)

    def _use_link(self, actor, links, use_pos):
        try:
            pos = actor.pos if use_pos else None
            world, pos = links[pos]
            self.remove_actor(actor)
            world.add_actor(actor, pos)
            return True
        except KeyError:
            return False

    def _stairable(self, pos):
        return self.passable_at(pos) and not self.stairs_at(pos)

    def _get_stairable(self):
        return self.get_random_tile(self._stairable)

    def stairs_at(self, pos):
        """
        RuinWorld.stairs_at(Coordinate): bool
        Returns True if there are stairs at the given location on the RuinWorld
        """

        return pos in self._downlinks or pos in self._uplinks

    def ascend(self, actor, use_pos):
        """
        RuinWorld.ascend(Actor, bool): None
        Tries to move the Actor upstairs from this World. Overriden by
        subclasses to indicate whether physical stairs are required.
        """

        return self._use_link(actor, self._uplinks, use_pos)

    def descend(self, actor, use_pos):
        """
        RuinWorld.ascend(Actor, bool): None
        Tries to move the Actor downstairs from this World. Overriden by
        subclasses to indicate whether physical stairs are required.
        """

        return self._use_link(actor, self._downlinks, use_pos)


class Overworld(RuinWorld):
    """Represents the entire game world in Ruin"""

    def __init__(self, cols, rows):
        RuinWorld.__init__(self, cols, rows)
        mapping = resource.terrain_data.get_heights()
        terrain.overworld(self, mapping)
        terrain.fence(self)

    def ascend(self, actor):
        actor.append_message('%s <can> not fly', actor)

    def descend(self, actor):
        if not self.stairs_at(actor.pos):
            pos = types.Coordinate.copy(actor.pos)
            other = Region('forest', 40, 40)
            self._connect_worlds(other, pos, None, None, None)

        RuinWorld.descend(self, actor, True)


class Region(RuinWorld):
    """A single region of the Ruin overworld"""

    def __init__(self, name, cols, rows):
        RuinWorld.__init__(self, cols, rows)
        terrain_data = resource.terrain_data.apply_terrain(name)
        terrain.apply_terrain(self, terrain_data)
        terrain.fence(self)

        for _ in range(5):
            self.add_actor(resource.create_monster('wolf'))

        for _ in range(dice.sample_poisson(2)):
            cave_depth = dice.sample_poisson(.5) + 1
            level = self
            down = types.ColoredChar('>')
            up = types.ColoredChar('<')
            for depth in range(cave_depth):
                new = Cave(20, 20, depth)
                pos = level._get_stairable()
                new_pos = new._get_stairable()
                level._connect_worlds(new, pos, new_pos, down, up)
                level = new

        self.add_actor(resource.create_item('bow'))

    def ascend(self, actor):
        RuinWorld.ascend(self, actor, False)

    def descend(self, actor):
        if RuinWorld.descend(self, actor, True):
            actor.append_message('%s <enter> a cave', actor)
        else:
            actor.append_message('%s <dig> in the dirt a little', actor)

class Cave(RuinWorld):
    """A single cave level"""

    def __init__(self, cols, rows, depth):
        RuinWorld.__init__(self, cols, rows)
        cave.cavern(self, types.ColoredChar('#', sprite_id=4),
                          types.ColoredChar('.', sprite_id=5))

        for _ in range(3 + depth * 2):
            self.add_actor(resource.create_monster('goblin'))
        for _ in range(depth):
            self.add_actor(resource.create_monster('orc'))

        self.add_actor(resource.create_item('sword'))
        self.add_actor(resource.create_item('dagger'))
        self.add_actor(resource.create_item('chain mail'))
        self.add_actor(resource.create_item('full plate'))
        self.add_actor(resource.create_item('trinket'))
        self.add_actor(resource.create_item('bouble'))

    def ascend(self, actor):
        if RuinWorld.ascend(self, actor, True):
            actor.append_message('%s <leave> the cave', actor)
        else:
            actor.append_message('%s <see> no cave exit', actor)

    def descend(self, actor):
        if RuinWorld.descend(self, actor, True):
            actor.append_message('%s <enter> a new level', actor)
        else:
            actor.append_message('%s <see> no stairs', actor)
